47  创建N维数组

47.1 引言NumPy数组的数学基础

理论背景:张量(Tensor)与多维数组

NumPy(Numerical Python)的核心数据结构是ndarray(N-dimensional array),它实现了张量(Tensor)的概念。从数学的角度:

  1. 标量(Scalar): 0维张量,如\(5\)
  2. 向量(Vector): 1维张量,如\([1, 2, 3]\)
  3. 矩阵(Matrix): 2维张量,如\([[1, 2], [3, 4]]\)
  4. 高维张量: 3维及以上,如时间序列数据立方体

数学表示:张量索引

对于\(d\)维张量\(\mathbf{A}\),元素索引为: \[ \mathbf{A}_{i_1, i_2, \ldots, i_d} \]

其中每个索引\(i_k\)的范围是\(0 \leq i_k < n_k\)(\(n_k\)是该维度的大小)。

NumPy数组的内存模型:

特性 NumPy数组 Python列表
内存布局 连续内存块 分散的对象引用
数据类型 同构(所有元素类型相同) 异构(可混合)
元素访问 O(1)直接访问 O(1)通过引用
内存效率 紧凑存储(如int32占4字节) 每个对象约28字节开销
计算性能 向量化操作(C层面) 解释执行(Python层面)

补充说明:为什么NumPy如此重要?

NumPy提供了Python中缺失的高性能数值计算能力: 1. 向量化运算: 避免Python循环,利用CPU的SIMD指令 2. 广播机制: 自动对齐不同形状的数组 3. C语言底层: 核心代码用C实现,速度接近编译语言 4. 丰富的函数库: 线性代数、傅里叶变换、随机数生成等

47.2 创建一维数组

平台任务解答代码

以下代码与教学平台任务要求完全一致:

列表 47.1
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一
import numpy as np #导入NumPy模块并且用英文缩写np

price_amazon= np.array([170.23,170.10,177.59,177.06,178.22])    #直接创建一维数组

type (price_amazon)  # 查看price_amazon的数据类型

price_app1e=[221.27,221.72,224.72,226.05,225.89]  # 定义列表price_app1e

price_apple=np.array(price_app1e)                 #将列表转换为一维数组 

type(price_apple)  # 查看price_apple的数据类型

#任务二
import numpy as np

data1 = [170.23,170.10,177.59,177.06,178.22]  # 定义列表data1
data2 = [221.27,221.72,224.72,226.05,225.89]  # 定义列表data2
data3 = [413.26,416.11,421.03,418.47,421.53]  # 定义列表data3
data4 = [164.16,160.37,161.30,162.96,166.67]  # 定义列表data4
data5 = [648.02,661.68,663.22,674.07,688.53]  # 定义列表data5

price_array=np.array([data1,data2,data3,data4,data5]) #将以上列表数据创建成数组

print(price_array.shape)  #查看数组的形状
print(price_array.ndim)  #查看数组的维度
print(price_array.size)  #查看数组的元素个数
print(price_array.dtype)  #查看数组中的元素类型
  

#任务三
import numpy as np

price_app1e=[221.27,221.72,224.72,226.05,225.89]  # 定义列表price_app1e

n=price_array.size  #变量n赋值为数组的元素个数

data1 = [170.23,170.10,177.59,177.06,178.22]  # 定义列表data1
data2 = [221.27,221.72,224.72,226.05,225.89]  # 定义列表data2
data3 = [413.26,416.11,421.03,418.47,421.53]  # 定义列表data3
data4 = [164.16,160.37,161.30,162.96,166.67]  # 定义列表data4
data5 = [648.02,661.68,663.22,674.07,688.53]  # 定义列表data5

price_array=np.array([data1,data2,data3,data4,data5]) #将以上列表数据创建成数组

n=price_array.size  #变量n赋值为数组的元素个数

x=np.arange(n+1)  #生成整数序列
m=40  # 设置数组长度参数为40
y=np.linspace(price_apple[0],price_apple[-1],m)#生成起始值是苹果公司股票5月13日收盘价、终止值是5月17日收盘价且元素数量40的等差序列
print(y)  # 输出变量y的值

#任务四
import numpy as np

price_app1e=[221.27,221.72,224.72,226.05,225.89]  # 定义列表price_app1e

price_amazon=np.array([1822.68,1840,12,1871.15,1907.57,1869.00])  # 创建NumPy数组price_amazon

zero_arrayl=np.zeros_like(price_amazon)#快速生成元素为零的一维数组
zero_array2=np.zeros_like(price_array)#快速生成元素为零的二维数组
one_arrayl=np.ones_like(price_amazon)#快速生成元素等于1的一维数组
one_array2=np.ones_like(price_array) #快速生成元素等于1的二维数组
print(zero_arrayl)  # 输出变量zero_arrayl的值
print(zero_array2)  # 输出变量zero_array2的值
print(one_arrayl)  # 输出变量one_arrayl的值
print(one_array2)  # 输出变量one_array2的值
列表 47.2
# 导入NumPy库
# np是NumPy的通用别名,符合社区约定
import numpy as np

# 方法1:从Python列表创建
# np.array()将列表转换为NumPy数组
# [1, 2, 3, 4, 5]是Python列表
arr1 = np.array([1, 2, 3, 4, 5])

# 方法2:使用arange()创建
# np.arange()类似于Python的range(),但返回NumPy数组
# arange(10)生成[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
arr2 = np.arange(10)

# 方法3:使用linspace()创建等间距数列
# np.linspace(start, stop, num)生成从start到stop的num个等间距点
# 包含起始值和结束值
# linspace(0, 1, 5)生成[0., 0.25, 0.50, 0.75, 1.00]
arr3 = np.linspace(0, 1, 5)

# 输出数组内容
print('arange:', arr2)
print('linspace:', arr3)

代码深度解析:

  1. arange() vs linspace()的区别:

    # arange(): 指定步长
    # 语法: np.arange(start, stop, step)
    arr_arange = np.arange(0, 1, 0.2)
    print(arr_arange)
    # [0., 0.2, 0.4, 0.6, 0.8] (不包含1.0)
    
    # linspace(): 指定元素个数
    # 语法: np.linspace(start, stop, num)
    arr_linspace = np.linspace(0, 1, 6)
    print(arr_linspace)
    # [0., 0.2, 0.4, 0.6, 0.8, 1.0] (包含1.0)
    
    # 金融应用:生成时间序列
    # linspace确保包含起始和结束时间点
    # 适合生成固定频率的时间索引
  2. 数据类型的自动推断:

    # NumPy自动推断数据类型
    int_arr = np.array([1, 2, 3])
    print(int_arr.dtype)  # int32或int64(取决于系统)
    
    float_arr = np.array([1.0, 2.0, 3.0])
    print(float_arr.dtype)  # float64
    
    # 显式指定数据类型
    float_arr_int32 = np.array([1, 2, 3], dtype=np.float32)
    print(float_arr_int32.dtype)  # float32
    
    # 为什么要指定数据类型?
    # - 内存控制: float32比float64节省一半内存
    # - 精度需求: 某些应用需要更高精度
    # - 兼容性: 与C/C++库交互时
  3. 数组与列表的本质区别:

    # 列表:存储对象的引用
    python_list = [1, 2, 3]
    print(python_list[0])  # 1
    python_list[0] = 100
    print(python_list)  # [100, 2, 3]
    
    # NumPy数组:存储实际的数值
    numpy_arr = np.array([1, 2, 3])
    print(numpy_arr[0])  # 1
    numpy_arr[0] = 100
    print(numpy_arr)  # [100, 2, 3]
    
    # 但NumPy数组的元素类型必须一致
    # 这与列表不同
    mixed_list = [1, 'hello', 3.14]  # 合法
    # mixed_arr = np.array([1, 'hello', 3.14])  # 全部转为字符串
  4. 金融应用:生成交易日序列:

    import pandas as pd
    
    # 生成252个交易日(假设一年)
    # 使用arange生成索引
    trading_days = np.arange(252)
    
    # 生成日期范围
    dates = pd.date_range('2024-01-01', periods=252, freq='B')
    
    # 截取实际交易日(排除节假日)
    # 实际应用中会少于252天
    actual_dates = dates[:len(trading_days)]
    
    print(f'前10个交易日: {actual_dates[:10]}')
  5. 数组的内存占用:

    import sys
    
    # 比较内存占用
    python_list = list(range(100000))
    numpy_arr = np.arange(100000)
    
    list_size = sys.getsizeof(python_list)
    arr_size = sys.getsizeof(numpy_arr)
    
    print(f'列表大小: {list_size / 1024:.2f} KB')
    print(f'数组大小: {arr_size / 1024:.2f} KB')
    
    # NumPy数组的优势在大规模数据时更明显
    # 100万个整数:
    # - 列表: ~28 MB (每个int对象约28字节)
    # - 数组: ~8 MB (每个int32占4字节)

47.3 创建二维数组

列表 47.3
# 方法1:从列表的列表创建
# 列表的列表代表二维结构
# 内层每个列表代表矩阵的一行
arr2d = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
])

# 方法2:使用reshape()重塑数组
# 首先创建一个一维数组: [0, 1, 2, ..., 10, 11]
arr_flat = np.arange(12)

# reshape()改变数组的形状,不改变数据
# (3, 4)表示重塑为3行4列的矩阵
# 元素总数必须匹配(3×4=12)
arr_3x4 = arr_flat.reshape(3, 4)

print('2D数组(3x3):')
print(arr2d)

print(f'\n3x4数组:')
print(arr_3x4)

代码深度解析:

  1. 数组形状的理解:

    # 数组的shape属性返回各维度的大小
    arr = np.array([[1, 2, 3], [4, 5, 6]])
    print(arr.shape)  # (2, 3) - 2行3列
    
    # 形状的直观理解:
    # 第0维(行): 有2个元素
    # 第1维(列): 每行有3个元素
    #
    # 可视化为:
    # [[1, 2, 3],   <- 第0行
    #  [4, 5, 6]]   <- 第1行
    
    # 访问特定位置的元素
    print(arr[0, 1])  # 2 - 第0行,第1列
    print(arr[1, 2])  # 6 - 第1行,第2列
  2. reshape()的规则:

    # 规则:重塑前后的元素总数必须相等
    arr = np.arange(12)  # 12个元素
    
    # 合法的重塑
    print(arr.reshape(3, 4))   # 3×4=12 ✓
    print(arr.reshape(4, 3))   # 4×3=12 ✓
    print(arr.reshape(2, 6))   # 2×6=12 ✓
    # print(arr.reshape(5, 3))   # 5×3=15 ✗ 错误!
    
    # -1作为自动推断
    # reshape(3, -1)表示:3行,列数自动计算
    print(arr.reshape(3, -1))  # (3, 4) - 推断出4列
    print(arr.reshape(-1, 4))  # (3, 4) - 推断出3行
    
    # 为什么使用-1?
    # - 方便:不需要手动计算
    # - 灵活:修改一个维度时另一个自动调整
  3. 转置操作:

    # .T属性或transpose()方法实现矩阵转置
    arr = np.array([[1, 2, 3], [4, 5, 6]])
    
    print('原数组:')
    print(arr)
    print(f'形状: {arr.shape}')
    
    print('\n转置后:')
    print(arr.T)
    print(f'形状: {arr.T.shape}')
    
    # 转置的数学意义:
    # 如果arr是m×n矩阵
    # 则arr.T是n×m矩阵
    # 元素[i, j]变为元素[j, i]
  4. 金融应用:收益率矩阵:

    # 假设有3只股票,5个交易日的收益率
    # 每行代表一只股票,每列代表一个交易日
    returns_3x5 = np.array([
        [0.01, -0.02, 0.03, 0.01, -0.01],  # 股票1
        [0.02, 0.01, -0.01, 0.03, 0.02],    # 股票2
        [-0.01, 0.02, 0.01, -0.02, 0.01]    # 股票3
    ])
    
    # 计算每只股票的平均收益率
    # axis=1表示沿第1维(列)操作,即对每行求平均
    avg_returns = returns_3x5.mean(axis=1)
    print(f'平均收益率: {avg_returns}')
    
    # 计算每天的收益率均值
    # axis=0表示沿第0维(行)操作,即对每列求平均
    daily_avg = returns_3x5.mean(axis=0)
    print(f'每日均值: {daily_avg}')
  5. 多维数组的可视化理解:

    # 3维数组:时间序列数据立方体
    # 形状:(股票数, 交易日数, 数据点数)
    # 例如: (100, 252, 4)
    # - 100只股票
    # - 每只股票252个交易日的数据
    # - 每天4个数据点(开盘、最高、最低、收盘)
    
    arr_3d = np.random.randn(100, 252, 4)
    
    # 提取第一只股票的所有数据
    stock_0 = arr_3d[0, :, :]  # shape: (252, 4)
    
    # 提取第一天的所有股票收盘价(假设是第3列)
    close_day_0 = arr_3d[:, 0, 3]  # shape: (100,)
    
    # 提取第一只股票第一天的数据
    stock_0_day_0 = arr_3d[0, 0, :]  # shape: (4,)

47.4 创建特殊数组

列表 47.4
# 1. 全0数组
# np.zeros()创建所有元素为0的数组
# 参数可以是整数(一维)或元组(多维)
zeros = np.zeros((2, 3))
print('全0数组(2x3):')
print(zeros)

# 2. 全1数组
# np.ones()创建所有元素为1的数组
ones = np.ones((3, 4))
print('\n全1数组(3x4):')
print(ones)

# 3. 单位数组(单位矩阵)
# np.eye(n)创建n×n的单位矩阵
# 单位矩阵:对角线为1,其余为0
identity = np.eye(3)
print('\n单位矩阵(3x3):')
print(identity)

# 4. 对角矩阵
# np.diag()创建对角矩阵或提取对角元素
diag = np.diag([1, 2, 3])
print('\n对角矩阵:')
print(diag)

# 5. 随机数组
# np.random.randn()生成标准正态分布的随机数
# 均值0,标准差1
random_arr = np.random.randn(2, 3)
print('\n随机数组(2x3):')
print(random_arr)

代码深度解析:

  1. 特殊数组的数学意义:

    # 全0数组:零矩阵
    # 在线性代数中,零矩阵O满足: O + A = A
    zero_matrix = np.zeros((3, 3))
    
    # 全1数组:所有元素为1的矩阵
    ones_matrix = np.ones((3, 3))
    
    # 单位矩阵:I
    # 单位矩阵在矩阵乘法中充当1的角色: I·A = A·I = A
    I = np.eye(3)
    
    # 验证单位矩阵的性质
    A = np.array([[1, 2], [3, 4]])
    print(np.dot(I, A))  # 等于A
    print(np.dot(A, I))  # 等于A
  2. 对角矩阵的应用:

    # 对角矩阵在协方差矩阵中的应用
    # 对角线是方差,非对角线是协方差
    
    # 假设3只资产,彼此独立
    # 对角线是各资产的方差
    variances = [0.01, 0.02, 0.015]  # 1%, 2%, 1.5%
    cov_matrix = np.diag(variances)
    
    print('协方差矩阵(独立资产):')
    print(cov_matrix)
    # [[0.01, 0.   , 0.    ],
    #  [0.   , 0.02 , 0.    ],
    #  [0.   , 0.   , 0.015 ]]
    
    # 金融应用:计算投资组合方差
    # 方差 = w^T · Σ · w
    # 其中w是权重向量,Σ是协方差矩阵
    weights = np.array([0.4, 0.3, 0.3])
    portfolio_var = np.dot(weights, np.dot(cov_matrix, weights))
    print(f'投资组合方差: {portfolio_var:.6f}')
  3. 随机数生成:

    # 标准正态分布: N(0, 1)
    # mean=0, std=1
    standard_normal = np.random.randn(1000)
    
    # 均匀分布: U(0, 1)
    # [0, 1]区间上的均匀分布
    uniform = np.random.rand(1000)
    
    # 正态分布: N(μ, σ²)
    # 均值μ,标准差σ
    normal = np.random.normal(loc=0.001, scale=0.02, size=1000)
    # loc:均值, scale:标准差
    
    # 金融应用:模拟股票收益率
    # 假设日收益率服从正态分布
    # 均值0.05%(年化约13%),标准差2%(年化约32%)
    daily_returns = np.random.normal(0.0005, 0.02, 252)
    
    # 计算年化收益率
    # 简单近似: 年化收益率 ≈ 日收益率 × 252
    annualized_return = daily_returns.mean() * 252
    print(f'年化收益率: {annualized_return:.2%}')
    
    # 年化波动率
    # 年化波动率 = 日波动率 × √252
    annualized_vol = daily_returns.std() * np.sqrt(252)
    print(f'年化波动率: {annualized_vol:.2%}')
  4. 单位矩阵的特殊性质:

    I = np.eye(3)
    
    # 性质1: I的行列式为1
    print(f'行列式: {np.linalg.det(I):.0f}')
    
    # 性质2: I的迹(trace,对角元素之和)为维度
    print(f'迹: {np.trace(I)}')  # 1+1+1 = 3
    
    # 性质3: I的特征值全为1
    eigenvalues, _ = np.linalg.eig(I)
    print(f'特征值: {eigenvalues}')
    
    # 性质4: I是正定矩阵
    # 正定矩阵:所有特征值>0
    print(np.all(eigenvalues > 0))  # True
  5. 空数组与标量扩展:

    # 空数组
    empty = np.array([])
    print(f'空数组形状: {empty.shape}')  # (0,)
    
    # 标量扩展(广播)
    scalar = 5
    arr = np.array([1, 2, 3])
    result = scalar + arr  # 标量5扩展为[5, 5, 5]
    
    print(f'标量扩展: {result}')
    # [6, 7, 8]
    
    # 这种机制称为"广播"(Broadcasting)
    # 它使得不同形状的数组可以进行运算

47.5 数组属性

列表 47.5
# 创建一个2×3的数组
arr = np.array([[1, 2, 3], [4, 5, 6]])

# 1. shape属性:数组形状
# 返回元组,表示各维度的大小
print('形状:', arr.shape)  # (2, 3) - 2行3列

# 2. ndim属性:数组维度
# 返回整数,表示维度的数量
print('维度:', arr.ndim)  # 2 - 二维数组

# 3. size属性:元素总数
# 返回整数,表示数组中所有元素的个数
print('大小:', arr.size)  # 6 - 2×3=6个元素

# 4. dtype属性:数据类型
# 返回numpy.dtype对象,描述数组的数据类型
print('数据类型:', arr.dtype)  # int32或int64

# 5. reshape():改变数组形状
# (3, 2)表示重塑为3行2列
# 元素总数必须保持不变(6个)
arr_reshaped = arr.reshape(3, 2)

print('\n重塑后(3x2):')
print(arr_reshaped)

代码深度解析:

  1. shape属性的详细解释:

    # 3维数组的shape
    arr_3d = np.random.randn(4, 5, 6)
    print(arr_3d.shape)  # (4, 5, 6)
    
    # 含义:
    # - 第0维(深度): 4个元素
    # - 第1维(行): 每个深度5行
    # - 第2维(列): 每行6列
    # 总元素数: 4×5×6 = 120
    
    # 维度命名(约定俗成):
    # - axis=0: 第0维,对3维数组是"深度"
    # - axis=1: 第1维,对3维数组是"行"
    # - axis=2: 第2维,对3维数组是"列"
    
    # 沿不同维度的操作
    print(f'沿axis=0求和:\n{arr_3d.sum(axis=0)}')   # (5, 6)
    print(f'沿axis=1求和:\n{arr_3d.sum(axis=1)}')   # (4, 6)
    print(f'沿axis=2求和:\n{arr_3d.sum(axis=2)}')   # (4, 5)
  2. ndim与维度的关系:

    # 0维数组(标量)
    scalar = np.array(5)
    print(scalar.ndim)  # 0
    
    # 1维数组(向量)
    vector = np.array([1, 2, 3])
    print(vector.ndim)  # 1
    
    # 2维数组(矩阵)
    matrix = np.array([[1, 2], [3, 4]])
    print(matrix.ndim)  # 2
    
    # 3维数组(张量)
    tensor = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
    print(tensor.ndim)  # 3
    
    # 为什么维度很重要?
    # - 维度决定了数据的组织方式
    # - 不同维度的操作有不同的axis参数
    # - 高维数据需要特殊的处理方法
  3. size与内存计算:

    arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32)
    
    # size返回元素个数
    print(arr.size)  # 6
    
    # nbytes返回字节数
    print(arr.nbytes)  # 24 = 6个元素 × 4字节/元素
    
    # 计算内存占用(MB)
    memory_mb = arr.nbytes / (1024 * 1024)
    print(f'内存占用: {memory_mb:.3f} MB')
    
    # 大规模数组的内存估算
    # 假设1000×1000的float64数组
    large_arr = np.zeros((1000, 1000), dtype=np.float64)
    memory_mb = large_arr.nbytes / (1024 * 1024)
    print(f'1000×1000数组占用: {memory_mb:.2f} MB')
    # 输出: 7.63 MB
    
    # 这就是为什么NumPy适合大规模计算
    # 相比之下,Python列表会占用数十倍内存
  4. dtype的深入理解:

    # NumPy支持丰富的数据类型
    # 整数类型: int8, int16, int32, int64
    # 无符号整数: uint8, uint16, uint32, uint64
    # 浮点类型: float16, float32, float64
    # 复数类型: complex64, complex128
    
    # 整数类型的范围
    print(f'int8范围: [{np.iinfo(np.int8).min}, {np.iinfo(np.int8).max}]')
    print(f'int16范围: [{np.iinfo(np.int16).min}, {np.iinfo(np.int16).max}]')
    
    # 浮点类型的精度
    print(f'float32精度: {np.finfo(np.float32).eps}')  # 约1.2e-7
    print(f'float64精度: {np.finfo(np.float64).eps}')  # 约2.2e-16
    
    # 金融应用:为什么要用float64?
    # - 金融计算需要高精度
    # - 避免累积误差
    # - 符合行业标准(IEEE 754)
    
    # 何时可以用float32?
    # - 内存受限时(如图像处理)
    # - 精度要求不高时
    # - 性能优先时(float32运算更快)
  5. reshape的高级用法:

    # 展平数组
    arr = np.array([[1, 2, 3], [4, 5, 6]])
    
    # 方法1:ravel()返回视图(view)
    flat_view = arr.ravel()
    flat_view[0] = 100
    print(arr[0, 0])  # 100 - 原数组也被修改!
    
    # 方法2:flatten()返回副本(copy)
    flat_copy = arr.flatten()
    flat_copy[0] = 200
    print(arr[0, 0])  # 100 - 原数组不受影响
    
    # 多维重塑
    arr = np.arange(24)
    arr_3d = arr.reshape(2, 3, 4)  # 2×3×4的3维数组
    print(arr_3d.shape)
    
    # transpose()交换维度
    # (2, 3, 4) -> (4, 3, 2)
    arr_transposed = arr_3d.transpose(2, 1, 0)
    print(arr_transposed.shape)

47.6 数组运算

列表 47.6
# 创建两个一维数组
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 1. 基本算术运算
# NumPy的运算是元素级的(element-wise)
# 即对应位置的两个元素分别运算

print('加法:', a + b)
# 输出: [5 7 9]  # [1+4, 2+5, 3+6]

print('减法:', a - b)
# 输出: [-3 -3 -3]  # [1-4, 2-5, 3-6]

print('乘法:', a * b)
# 输出: [4 10 18]  # [1×4, 2×5, 3×6]
# 注意:这不是矩阵乘法!

print('除法:', a / b)
# 输出: [0.25 0.4  0.5 ]  # [1/4, 2/5, 3/6]

print('幂运算:', a ** 2)
# 输出: [1 4 9]  # [1², 2², 3²]

# 2. 统计运算
# 这些是NumPy数组的方法,对整个数组进行统计

print(f'\n均值: {a.mean():.2f}')
# 输出: 2.00  # (1+2+3)/3

print(f'标准差: {a.std():.2f}')
# 输出: 0.82  # √[((1-2)²+(2-2)²+(3-2)²]/3]

print(f'方差: {a.var():.2f}')
# 输出: 0.67  # [(1-2)²+(2-2)²+(3-2)²]/3

print(f'最大值: {a.max()}')
# 输出: 3

print(f'最小值: {a.min()}')
# 输出: 1

# 求和
print(f'求和: {a.sum()}')
# 输出: 6

代码深度解析:

  1. 向量化运算的数学意义:

    # Python列表运算 vs NumPy数组运算
    
    # Python列表:元素级相加需要循环
    list_a = [1, 2, 3]
    list_b = [4, 5, 6]
    
    # 方法1:for循环
    result = []
    for i in range(len(list_a)):
        result.append(list_a[i] + list_b[i])
    
    # 方法2:列表推导式
    result = [list_a[i] + list_b[i] for i in range(len(list_a))]
    
    # NumPy数组:向量化运算
    arr_a = np.array([1, 2, 3])
    arr_b = np.array([4, 5, 6])
    result = arr_a + arr_b  # 一行代码!
    
    # 性能差异:
    # - Python循环:解释执行,每个元素都要类型检查
    # - NumPy向量化:C层面,直接对内存块操作
    # 对于100万个元素,NumPy可能快100倍
  2. 广播机制(Broadcasting):

    # 广播:不同形状的数组可以运算
    # 规则:从尾部(最右边)开始,逐维度比较
    
    # 示例1:标量与数组
    scalar = 5
    arr = np.array([1, 2, 3])
    result = scalar + arr
    print(result)  # [6, 7, 8]
    # 标量5扩展为[5, 5, 5]
    
    # 示例2:不同维度的数组
    arr1 = np.array([[1], [2], [3]])  # (3, 1)
    arr2 = np.array([10, 20, 30])        # (3,)
    result = arr1 + arr2
    print(result)
    # arr1扩展为[[1,1,1], [2,2,2], [3,3,3]]
    # arr2扩展为[[10,20,30], [10,20,30], [10,20,30]]
    # 相加后: [[11,21,31], [12,22,32], [13,23,33]]
    
    # 示例3:不兼容的形状
    arr1 = np.array([1, 2, 3])  # (3,)
    arr2 = np.array([10, 20])     # (2,)
    # result = arr1 + arr2  # 错误!形状不兼容
  3. 矩阵乘法 vs 元素乘法:

    # 元素乘法(*):对应元素相乘
    a = np.array([[1, 2], [3, 4]])
    b = np.array([[5, 6], [7, 8]])
    
    element_mul = a * b
    print('元素乘法:')
    print(element_mul)
    # [[ 5, 12],
    #  [21, 32]]
    
    # 矩阵乘法(@):线性代数的矩阵乘法
    mat_mul = a @ b  # 或 np.dot(a, b)
    print('\n矩阵乘法:')
    print(mat_mul)
    # [[1×5+2×7, 1×6+2×8],
    #  [3×5+4×7, 3×6+4×8]]
    # = [[19, 22],
    #    [43, 50]]
    
    # 金融应用:投资组合收益
    # weights: (3,) 权重向量
    # returns: (3, 5) 3只股票5天的收益率矩阵
    weights = np.array([0.3, 0.5, 0.2])
    returns = np.random.randn(3, 5)
    
    # 每天的投资组合收益
    portfolio_returns = weights @ returns
    print(f'组合收益率: {portfolio_returns}')
  4. 统计函数的axis参数:

    arr = np.array([
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
    ])
    
    # 不指定axis:对所有元素求统计量
    print(f'全局均值: {arr.mean()}')  # 5.0
    
    # axis=0:沿列方向(垂直)
    print(f'列均值(向下): {arr.mean(axis=0)}')
    # [4., 5., 6.]  # 每列的均值
    
    # axis=1:沿行方向(水平)
    print(f'行均值(向右): {arr.mean(axis=1)}')
    # [2., 5., 8.]  # 每行的均值
    
    # 累积运算
    print(f'累积和(沿行):')
    print(arr.cumsum(axis=1))
    # [[ 1,  3,  6],
    #  [ 4,  9, 15],
    #  [ 7, 15, 24]]
    
    # 金融应用:计算累计收益率
    # daily_returns是日收益率序列
    daily_returns = np.array([0.01, -0.02, 0.03, 0.01, -0.01])
    
    # 累计收益率 = (1+r1)×(1+r2)×...×(1+rn) - 1
    cumulative_returns = (1 + daily_returns).cumprod() - 1
    print(f'累计收益率: {cumulative_returns}')
  5. 比较运算与布尔索引:

    arr = np.array([1, 2, 3, 4, 5])
    
    # 比较运算返回布尔数组
    mask = arr > 3
    print(f'布尔掩码: {mask}')  # [False, False, False, True, True]
    
    # 使用布尔索引筛选元素
    filtered = arr[mask]
    print(f'筛选结果: {filtered}')  # [4, 5]
    
    # 金融应用:找出上涨的交易日
    returns = np.array([0.01, -0.02, 0.03, -0.01, 0.02])
    
    # 正收益的掩码
    positive_mask = returns > 0
    print(f'正收益掩码: {positive_mask}')
    
    # 统计正收益天数
    positive_days = np.sum(positive_mask)
    print(f'正收益天数: {positive_days}/5')
    
    # 提取正收益
      positive_returns = returns[positive_mask]
    print(f'正收益率: {positive_returns}')

47.7 金融应用收益率计算

列表 47.7
# 价格序列
# 假设5个交易日的收盘价
prices = np.array([100, 105, 103, 108, 106])

# 1. 计算简单收益率(Simple Returns)
# 公式: R_t = (P_t - P_{t-1}) / P_{t-1}
# 使用切片:prices[1:]取第1到最后元素
#         prices[:-1]取第0到倒数第2个元素
# 这样两个数组的长度相同,可以对应计算
simple_returns = (prices[1:] - prices[:-1]) / prices[:-1]

# 2. 计算对数收益率(Log Returns)
# 公式: r_t = ln(P_t / P_{t-1}) = ln(1 + R_t)
# 对数收益率具有时间可加性
log_returns = np.log(prices[1:] / prices[:-1])

print('简单收益率:')
print(simple_returns)

print('\n对数收益率:')
print(log_returns)

# 3. 计算累计收益率
# 公式: 累计收益率 = (1+R_1)×(1+R_2)×...×(1+R_n) - 1
# cumprod()计算累积乘积
cum_returns = (1 + simple_returns).cumprod() - 1

print(f'\n累计收益率:')
print(cum_returns)

print(f'\n总收益率: {cum_returns[-1]:.4f}')
# 输出最后一个值(总收益率)

代码深度解析:

  1. 切片操作的数学意义:

    prices = np.array([100, 105, 103, 108, 106])
    
    # prices[1:]: 取索引1到末尾
    # Python切片语法: [start:stop:step]
    # 省略start表示从0开始
    # 省略stop表示到末尾
    prices_1_to_end = prices[1:]  # [105, 103, 108, 106]
    
    # prices[:-1]: 取索引0到倒数第2个
    # -1表示最后一个元素,所以:-1表示不包含最后一个
    prices_0_to_2last = prices[:-1]  # [100, 105, 103, 108]
    
    # 对应关系:
    # prices_1_to_end[0] = 105 <- prices_0_to_2last[1]
    # prices_1_to_end[1] = 103 <- prices_0_to_2last[2]
    # ...
  2. 简单收益率 vs 对数收益率:

    # 简单收益率
    R = 0.05
    # 缺点:不可跨时间相加
    # 两期收益率: R_total ≠ R_1 + R_2
    
    # 对数收益率
    r = np.log(1 + R)
    # 优点:可跨时间相加
    # 两期收益率: r_total = r_1 + r_2
    
    # 验证
    prices = np.array([100, 110, 105])
    
    # 方法1:简单收益率的复合
    R1 = (110 - 100) / 100  # 0.10
    R2 = (105 - 110) / 110  # -0.045...
    total_simple = (1 + R1) * (1 + R2) - 1  # 0.05
    print(f'简单收益率复合: {total_simple:.4f}')  # 0.0500
    
    # 方法2:对数收益率的相加
    r1 = np.log(110 / 100)
    r2 = np.log(105 / 110)
    total_log = r1 + r2
    print(f'对数收益率相加: {total_log:.4f}')  # 0.0500
    
    # 结果相等!但对数形式更容易处理
  3. 累计收益率的计算:

    # 假设日收益率序列
    returns = np.array([0.01, -0.02, 0.03, 0.01, -0.01])
    
    # 累计收益率的几何含义
    # 初始资金: 100元
    # 第1天后: 100 × (1+0.01) = 101
    # 第2天后: 101 × (1-0.02) = 98.98
    # 第3天后: 98.98 × (1+0.03) = 101.95
    # ...
    
    # NumPy的cumprod()计算累积乘积
    cumprod = (1 + returns).cumprod()
    print('累计乘积因子:')
    print(cumprod)
    
    # 累计收益率 = 累计乘积因子 - 1
    cum_returns = cumprod - 1
    print('\n累计收益率:')
    print(cum_returns)
    
    # 验证
    initial = 100
    final = initial * cumprod[-1]
    print(f'\n初始100元最终: {final:.2f}元')
    
    # 最大回撤计算
    # 最大回撤 = (峰值 - 当前值) / 峰值
    cum_returns_with_initial = np.insert(cumprod, 0, 1.0)
    peak = np.maximum.accumulate(cum_returns_with_initial)
    drawdown = (peak - cum_returns_with_initial) / peak
    max_drawdown = drawdown.max()
    print(f'最大回撤: {max_drawdown:.2%}')
  4. 年化收益率与波动率:

    # 假设有252个交易日的收益率数据
    np.random.seed(42)  # 设置随机种子,保证结果可复现
    daily_returns = np.random.normal(0.0005, 0.02, 252)
    
    # 年化收益率
    # 简单方法: 日均值 × 252
    annualized_return = daily_returns.mean() * 252
    print(f'年化收益率: {annualized_return:.2%}')
    
    # 年化波动率
    # 日标准差 × √252
    annualized_vol = daily_returns.std() * np.sqrt(252)
    print(f'年化波动率: {annualized_vol:.2%}')
    
    # 夏普比率(假设无风险利率为3%)
    risk_free_rate = 0.03
    sharpe_ratio = (annualized_return - risk_free_rate) / annualized_vol
    print(f'夏普比率: {sharpe_ratio:.2f}')
    
    # 索提诺比率(只考虑下行波动率)
    # 只取负收益计算标准差
    negative_returns = daily_returns[daily_returns < 0]
    downside_std = negative_returns.std() * np.sqrt(252)
    sortino_ratio = (annualized_return - risk_free_rate) / downside_std
    print(f'索提诺比率: {sortino_ratio:.2f}')
  5. 收益率的统计特性:

    # 正态性检验(JB检验)
    from scipy import stats
    
    # 生成收益率数据
    np.random.seed(42)
    returns = np.random.normal(0.001, 0.02, 1000)
    
    # JB检验:原假设是数据来自正态分布
    # p-value>0.05:不能拒绝原假设(可能来自正态分布)
    # p-value<0.05:拒绝原假设(不太可能来自正态分布)
    statistic, p_value = stats.jarque_bera(returns)
    
    print(f'JB统计量: {statistic:.2f}')
    print(f'p-value: {p_value:.4f}')
    
    if p_value > 0.05:
        print('收益率可能服从正态分布')
    else:
        print('收益率不服从正态分布')
    
    # 金融意义:
    # - 如果收益率服从正态分布,可以用传统参数方法
    # - 如果不服从,需要使用非参数方法或考虑厚尾分布

最佳实践总结:

  1. 选择合适的数据类型:
    • 金融计算优先使用float64(双精度)
    • 大数据集考虑float32(单精度)以节省内存
    • 整数数据选择足够大的类型避免溢出
  2. 充分利用向量化:
    • 避免Python循环,使用NumPy内置函数
    • 利用广播机制进行数组运算
    • 使用布尔索引替代条件判断
  3. 内存优化技巧:
    • 使用reshape()-1参数自动推断
    • 使用ravel()而非flatten()避免复制
    • 及时删除不再需要的大数组
  4. 数值稳定性:
    • 对数收益率避免小数精度问题
    • 使用np.log1p(x)计算log(1+x),提高小数值精度
    • 除法前检查分母是否为零
  5. 性能优化:
    • 预分配数组空间,避免动态扩展
    • 使用np.out参数存储中间结果
    • 对大规模数据使用分块计算